package com.amt.olcacon.controller.calc;

import com.amt.olcacon.db.DerbyConfig;
import com.amt.olcacon.entity.ExchangeEntity;
import com.amt.olcacon.entity.ProcessEntity;
import com.amt.olcacon.entity.ResultEntity;
import com.amt.olcacon.mapper.ExchangeMapper;
import com.amt.olcacon.mapper.FlowMapper;
import com.amt.olcacon.mapper.ProcessMapper;
import com.amt.olcacon.mapper.ResultMapper;
import com.amt.olcacon.utils.BeanCopyUtils;
import com.amt.olcacon.utils.OlcaUtils;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.openlca.core.DataDir;
import org.openlca.core.database.ImpactMethodDao;
import org.openlca.core.database.ProcessDao;
import org.openlca.core.math.Simulator;
import org.openlca.core.math.SystemCalculator;
import org.openlca.core.model.CalculationSetup;
import org.openlca.core.model.ImpactMethod;
import org.openlca.core.model.Process;
import org.openlca.core.model.descriptors.Descriptor;
import org.openlca.core.model.descriptors.ImpactDescriptor;
import org.openlca.core.results.*;
import org.openlca.core.results.providers.ResultProvider;
import org.openlca.nativelib.NativeLib;
import org.openlca.util.Results;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: yangyusheng
 * @Date: 2025/05/28
 */
@Slf4j
@RestController
@RequestMapping("olca")
public class CalcController {

    private final ResultMapper resultMapper;
    private final ProcessMapper processMapper;
    private final ExchangeMapper exchangeMapper;

    @Autowired
    public CalcController(
            ResultMapper resultMapper,
            ProcessMapper processMapper,
            ExchangeMapper exchangeMapper,
            FlowMapper flowMapper
    ) {
        this.resultMapper = resultMapper;
        this.processMapper = processMapper;
        this.exchangeMapper = exchangeMapper;
    }

    /**
     * 计算接口
     *
     * @param processId
     * @param methodId
     * @return
     */
    @GetMapping("/calc")
    public String calc(
            @RequestParam("processId") String processId,
            @RequestParam("methodId") String methodId
    ) {

        // 获取impactMethodRefId参数
        var db = DataDir.get().openDatabase(DerbyConfig.DBNAME);
        // 打开数据库
        Gson json = new Gson();

        // 创建Gson对象
        var system = db.get(Process.class, Long.parseLong(processId));
        // 获取processRefId对应的Process对象
        var method = new ImpactMethodDao(db).getForId(Long.parseLong(methodId));
        // 获取impactMethodRefId对应的ImpactMethod对象
        var setup = CalculationSetup.of(system)
                .withImpactMethod(method);
        // 创建CalculationSetup对象
        var calc = new SystemCalculator(db);
        // 创建SystemCalculator对象
        LcaResult lcaResult = calc.calculate(setup);
        // 计算结果
        db.close();
        // 关闭数据库
        return json.toJson(lcaResult);
        // 返回计算结果

    }

    /**
     * 计算接口
     *
     * @param processId
     * @param impactMethodId
     * @return
     */
    @GetMapping("/contributionTree")
    public String contributionTree(
            @RequestParam("processId") Long processId,
            @RequestParam("impactMethodId") Long impactMethodId
    ) {

        // 获取impactMethodRefId参数
        var db = DataDir.get().openDatabase(DerbyConfig.DBNAME);
        // 打开数据库
        Gson json = new Gson();

        // 创建Gson对象
        var system = db.get(Process.class, processId);
        // 获取processRefId对应的Process对象
        var method = new ImpactMethodDao(db).getForId(impactMethodId);
        // 获取impactMethodRefId对应的ImpactMethod对象
        var setup = CalculationSetup.of(system)
                .withImpactMethod(method);
        // 创建CalculationSetup对象
        var calc = new SystemCalculator(db);
        // 创建SystemCalculator对象
        LcaResult lcaResult = calc.calculate(setup);
        // 计算结果
//        var tree = getUpstreamNodes(lcaResult.provider(),);

        db.close();
        // 关闭数据库
        return json.toJson(lcaResult);
        // 返回计算结果

    }

    public List<UpstreamNode> getUpstreamNodes(ResultProvider provider, ImpactDescriptor impact) {
        var model = UpstreamTree.of(provider, impact);
        return model.childs(model.root);
    }

    @GetMapping("/categories")
    public List<String> categories(@RequestParam("impactMethodRefId") String impactMethodRefId) {
        var db = DataDir.get().openDatabase(DerbyConfig.DBNAME);

        var method = db.get(ImpactMethod.class, impactMethodRefId);
        List<String> result = new ArrayList<>();
        for (var categories : method.impactCategories) {
            result.add(categories.name);
        }
        db.close();
        return result;
    }

    @GetMapping("/simulator")
    public Map<String, Object> simulator(
            @RequestParam("processId") Long processId,
            @RequestParam("impactMethodId") Long impactMethodId,
            @RequestParam("results") Integer results,
            @RequestParam("gwpName") String gwpName
    ) {
        log.info("processId: {}, impactMethodId: {}, results: {}, gwpName: {}", processId, impactMethodId, results, gwpName);
        var db = DataDir.get().openDatabase(DerbyConfig.DBNAME);

        var system = db.get(Process.class, processId);
        var method = db.get(ImpactMethod.class, impactMethodId);
        var setup = CalculationSetup.of(system).withSimulationRuns(results).withImpactMethod(method);
        ImpactDescriptor gwp = null;
        for (var categories : method.impactCategories) {
            if (categories.name.equals(gwpName)) {
                gwp = Descriptor.of(categories);
                log.info("tacking results of {}", gwp.name);
                break;
            }
        }
        NativeLib.loadFrom(DataDir.get().root());
        Simulator simulator = Simulator.create(setup, db);

        double[] values = new double[results];
        for (int i = 0; i < results; i++) {
            var r = simulator.nextRun();
            double value = r.getTotalImpactValueOf(gwp);
            values[i] = value;
            r.dispose();
        }

        db.close();
        Statistics stats = Statistics.of(values);
        Map<String, Object> result = new HashMap<>();
        result.put("results", String.valueOf(results));
        result.put("values", values);
        //平均值
        result.put("mean", String.valueOf(stats.mean));
        //（标准差）方差的算术平方根
        result.put("standardDeviation", String.valueOf(stats.standardDeviation));
        //第5百分位数
        result.put("percentileValue5", String.valueOf(stats.getPercentileValue(5)));
        //第95百分位数
        result.put("percentileValue95", String.valueOf(stats.getPercentileValue(95)));
        //中位数
        result.put("median", String.valueOf(stats.median));
        //log.info("result:{}", result);
        return result;
    }

    @GetMapping("/saveAs")
    public Long addByProcess(@RequestParam("createProcess") Boolean createProcess,
                             @RequestParam("withMetaData") Boolean withMetaData,
                             @RequestParam("name") String name,
                             @RequestParam("processId") Long processId,
                             @RequestParam("impactMethodId") Long impactMethodId,
                             @RequestParam("description") String description) {

        try (var db = DataDir.get().openDatabase(DerbyConfig.DBNAME)) {
            Process process = new ProcessDao(db).getForId(processId);

            if (process == null) {
                log.info("流程不存在！");
                return null;
            }

            var system = db.get(Process.class, processId);
            // 获取processRefId对应的Process对象
            var method = new ImpactMethodDao(db).getForId(impactMethodId);
            // 获取impactMethodRefId对应的ImpactMethod对象
            var setup = CalculationSetup.of(system)
                    .withImpactMethod(method);
            // 创建CalculationSetup对象
            var calc = new SystemCalculator(db);
            // 创建SystemCalculator对象
            LcaResult lcaResult = calc.calculate(setup);

            Gson gson = new Gson();
            if (createProcess) {
                var sysProc = withMetaData ?
                        SystemProcess.createWithMetaData(db, setup, lcaResult, name) :
                        SystemProcess.create(db, setup, lcaResult, name);
                db.insert(sysProc);
                log.info("createProcess success! ProcessId is :{}", sysProc.id);
                //保存Process
                ProcessEntity pe = OlcaUtils.buildProcessEntity(sysProc);
                pe.setDescription( description);
                int count = processMapper.insert(pe);
                log.info("insert ProcessEntity success! count:{} pe:{}", count, gson.toJson(pe));

                // 保存 exchanges
                sysProc.exchanges.forEach(exchange -> {
                    ExchangeEntity ee = OlcaUtils.buildExchangeEntity(exchange, sysProc);
                    int c = exchangeMapper.insert(ee);
                    //log.info("insert ExchangeEntity success! count:{} ee:{}", c, gson.toJson(ee));
                });
                log.info("insert ExchangeEntity success! count:{}", sysProc.exchanges.size());
                return sysProc.id;
            } else {
                var result = Results.createFrom(db, setup, lcaResult);
                result.name = name;
                db.insert(result);
                log.info("createResults success! ResultsId is :{}", result.id);

                //保存Results
                ResultEntity re = BeanCopyUtils.copy(result, ResultEntity.class);
                re.setDescription(description);
                if (null != re.getName()) {
                    int count = resultMapper.insert(re);
                    log.info("insert ResultEntity success! count:{} re:{}", count, gson.toJson(re));
                }
                return result.id;
            }

        } catch (Exception e) {
            log.error("计算失败！", e);
            return 0L;
        }
    }


}
